package com.csaba.connector;

import java.util.ArrayList;
import java.util.Set;
import java.util.logging.Logger;

import com.csaba.connector.model.Bank;
import com.csaba.util.LoggerHelper;

/**
 * BankServiceFactory is used for instantiating BankServices.
 * <p> BankService classes are defined by banking plugins that can be added dynamically to the Csaba framework. 
 * The factory can work with different plugin mechanism (such as OSGi, or java.util.ServiceLoader) depending
 * on what is the FactoryProvider set initially. Here is code example: <code>
 * <pre>
 * BankServiceFactory.setProvider( new ServiceLoaderProvider());
 * Bank[] banks = BankServiceFactory.getAvailableBanks();
 * LoginService login = BankServiceFactory.getBankService( banks[0], LoginService.class); 
 * </pre></code>
 * <p> Bank plugins has to implement the ServicePluginConfiguration interface that provides information about 
 * the plugin, and helps producing service instancing.
 * <p> There are the following important methods:
 * <dl>
 * <dt>{@link #getAvailableBanks()}</dt><dd>Provides list of banks available as bank plugins.</dd>
 * <dt>{@link #getBankService(Bank, Class)}</dt><dd>Produces a bank service for the selected bank.</dd>
 * </dl>
 * <p> For sample bank plugin implementation see com.csaba.connector.dummy package.
 * 
 * <p> Open tasks:
 *  <ul>
 *  <li>TODO money transfer services</li>
 *  <li>TODO partner management</li>
 *  <li>TODO handle error message localization</li>
 *  <li>TODO introduce warnings in the bank services (for example: BHA adminMessages)</li>
 *  </ul>
 * @see {@link BankService}, {@link ServicePluginConfiguration}, {@link ClassEnumerationProvider}, {@link ServiceLoaderProvider}
 * @author gyenesge
 *
 */
public class BankServiceFactory
{
	private final static Logger logger = LoggerHelper.getDefaultLogger();

	public interface FactoryProvider
	{
		Set<ServicePluginConfiguration> getPlugins();
	}

	private static FactoryProvider provider = null;

	public static void setProvider( final FactoryProvider provider )
	{
		BankServiceFactory.provider = provider;
	}

	private static Set<ServicePluginConfiguration> pluginLoader = null;

	private static void initSessionLoader() throws ServiceException
	{
		if ( pluginLoader == null )
		{
			if ( provider == null )
				throw new ServiceException("Uninitialized BankServiceFactory. Set FactoryProvider first.");//FIXME I18N
			pluginLoader = provider.getPlugins();
		}
	}

	/**
	 * Collects list of bank plugins.
	 * @return list of available banks.
	 * @throws ServiceException 
	 */
	public static Bank[] getAvailableBanks() throws ServiceException
	{
		final ArrayList<Bank> banks = new ArrayList<Bank>();
		initSessionLoader();
		for ( final ServicePluginConfiguration plugin : pluginLoader )
		{
			banks.add(plugin.getBank());
		}
		return banks.toArray(new Bank[banks.size()]);
	}

	/**
	 * Produce a new instance of the required service class for the selected bank.
	 * @param <T> The required Sub interface of the BankService.
	 * @param bank Bank on which the service instance should be produced.
	 * @param serviceClass Class of the service interface. E.g. LoginService.class
	 * @return A new instance of the requested service class.
	 * @throws ServiceException This exception is thrown if any exception occures during the instantiation, or if the service is not supported by the bank.
	 */
	@SuppressWarnings( "unchecked" )
	public static <T extends BankService> T getBankService( final Bank bank, final Class<T> serviceClass )
			throws ServiceException
	{
		initSessionLoader();
		for ( final ServicePluginConfiguration plugin : pluginLoader )
		{
			if ( plugin.getBank().equals(bank) )
			{
				if ( plugin.getServicePackage() != null && plugin.getServicePrefix() != null )
				{
					final String className = plugin.getServicePackage() + "." + plugin.getServicePrefix()
							+ serviceClass.getSimpleName();
					try
					{
						final Class service = Class.forName(className);
						return (T) service.newInstance();
					}
					catch ( final Exception e )
					{
						logger.fine("Automatic class loading failed for " + serviceClass.getName() + " in bank "
								+ bank.getName() + " with exception " + e);
					}
				}
				return (T) plugin.getBankService(serviceClass);
			}
		}
		throw new ServiceException("Unsupported bank code: " + bank.getId());//FIXME I18N
	}

}
